<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <!-- 引入样式 -->
  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">

</head>

<body>
  <div id="app">
    <el-upload class="upload-demo" ref="upload" action="#" :on-change="handleBeforeUpload" :file-list="fileList"
      :auto-upload="false">
      <el-button slot="trigger" size="small" type="primary">选择文件</el-button>
      <el-button size="small" type="success" @click="startUpload">开始上传</el-button>
      <el-button size="small" type="warning" @click="pauseUpload">暂停上传</el-button>
    </el-upload>
    <el-progress style="width: 25%;" :percentage="uploadPercentage"></el-progress>
  </div>


</body>
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.5.0/axios.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.6/require.js"></script>
<script>
  new Vue({
    el: '#app',
    data() {
      return {
        // 存储取消令牌的数组
        cancelTokens: [],
        //进度条
        uploadPercentage: 0,
        //文件列表
        fileList: [],
        //文件对象
        file: null,
        //请求的域名
        requestUrl: "http://127.0.0.1:4001",
        //后台计算的唯一标识
        identifier: '',
        //是否正在上传
        isUploading: false,
        //文件切片数组
        chunks: [],
        //已上传文件分片的下标
        uploadedChunks: new Set(),
        totalUploaded: 0, // 总已上传的字节数
        totalSize: 0, // 文件的总字节数
        userId:123
      };
    },
    methods: {
      startUpload() {
        if (!this.file || this.isUploading) {
          alert('非法文件，禁止上传')
          return;
        }
        this.isUploading = true;
        this.fragmentUpload(this.file); // 调用上传逻辑
      },
      pauseUpload() {
        // 取消所有未完成的上传请求
        this.cancelTokens.forEach(source => source.cancel('Upload paused by the user.'));
        this.cancelTokens = []; // 清空令牌数组
        this.isUploading = false;
      },
      async handleBeforeUpload(file) {
        this.file = file.raw;
        this.uploadPercentage = 0;
        try {
          const response = await axios.get(this.requestUrl + '/generateIdentifier');
          this.identifier = response.data; // 存储从后端获取的标识符
          await this.checkUploadedChunks(); // 检查已上传的分片
        } catch (error) {
          console.error('Error fetching identifier:', error);
        }
        return false
      },
      async checkUploadedChunks() {
        try {
          const response = await axios.get(`${this.requestUrl}/upload/check`, {
            params: { fileName: this.file.name, identifier: this.identifier, userId: this.userId }
          });
          // 确保服务器总是返回一个数组，即使是空数组
          this.uploadedChunks = new Set(response.data);
        } catch (error) {
          // console.error('Error fetching uploaded chunks:', error);
          this.uploadedChunks = new Set(); // 在出错时确保重新初始化
        }
      },
      async fragmentUpload(file) {
        const chunkSize = 1 * 1024 * 1024; // 切片大小，1MB
        const chunkCount = Math.ceil(this.file.size / chunkSize);
        this.totalSize = this.file.size;
        this.totalUploaded = 0;
        const fileTotalSize = this.file.size;
        this.chunks = [];
        if (!this.uploadedChunks) {
          this.uploadedChunks = new Set(); // 再次确认初始化，以防万一
        }
        for (let i = 0; i < chunkCount; i++) {
          if (!this.uploadedChunks.has(i)) { // 检查分片是否已上传
            let chunk = this.file.slice(i * chunkSize, (i + 1) * chunkSize);
            this.chunks.push({
              index: i,
              blob: chunk,
              status: 'pending',
              size: chunk.size
            });
          }
        }

        // 并行上传未上传的分片
        this.chunks.forEach(chunk => this.uploadChunk(chunk, chunkCount, fileTotalSize, chunkSize));
      },
      async uploadChunk(chunk, chunkCount, fileTotalSize, chunkSize) {
        const formData = new FormData();
        formData.append("file", chunk.blob);
        formData.append("fileName", this.file.name);
        formData.append("chunkIndex", chunk.index);
        formData.append("identifier", this.identifier);
        formData.append("chunks", chunkCount);
        formData.append("totalSize", fileTotalSize);
        //当前片的大小
        formData.append("currentChunkSize", chunk.size);
        //分片大小
        formData.append("chunkSize", chunkSize);
        formData.append("userId", this.userId);
        // 创建一个新的取消令牌
        const CancelToken = axios.CancelToken;
        const source = CancelToken.source();

        // 将这个令牌存储起来，以便后续可能需要取消请求
        this.cancelTokens.push(source);
        try {
          const response = await axios.post(this.requestUrl + '/upload/fragment', formData, {
            cancelToken: source.token,
            onUploadProgress: progressEvent => {
              // 只更新尚未完成的部分
              if (progressEvent.loaded < progressEvent.total) {
                let newUploaded = progressEvent.loaded - (this.totalUploaded % chunkSize);
                this.totalUploaded += newUploaded;
              } else {
                // 处理最后一次更新
                let remaining = chunkSize - (this.totalUploaded % chunkSize);
                this.totalUploaded += remaining;
              }

              // 防止超过100%
              if (this.totalUploaded > this.totalSize) {
                this.totalUploaded = this.totalSize;
              }

              this.uploadPercentage = (this.totalUploaded / this.totalSize) * 100;
              // 格式化并确保不会超过100%
              let formattedPercentage = Math.min(100, this.uploadPercentage).toFixed(2);
              this.uploadPercentage = parseFloat(formattedPercentage);
            }
          }).then(response => {
            // this.cancelTokens.splice(source)
          }).catch(error => {
            if (axios.isCancel(error)) {
              console.log('Upload cancelled', error.message);
            } else {
              console.error('Upload failed', error);
            }
            // 处理请求失败的逻辑
            this.deleteFileChunks(fileName, this.identifier,this.userId);
          });
          chunk.status = 'done';
          this.checkAllChunksUploaded(chunkCount); // 检查是否所有分片都已上传
        } catch (error) {
          console.error('Error uploading chunk:', error);
          chunk.status = 'error';

        }
      },

      handleRemove(file, fileList) {
        console.log(file, fileList);
      },
      handlePreview(file) {
        console.log(file);
      },
      handleExceed(files, fileList) {
        this.$message.warning(`当前限制选择 3 个文件，本次选择了 ${files.length} 个文件，共选择了 ${files.length + fileList.length} 个文件`);
      },
      //校验md5秒传
      // async calculateFileMD5(file) {
      //   const reader = new FileReader();
      //   reader.readAsBinaryString(file);
      //   reader.onload = e => {
      //     const md5 = SparkMD5.hashBinary(e.target.result);
      //     console.log("文件MD5:", md5);
      //     // 可以在这里发送MD5到后端进行秒传检查
      //   };
      // },
      // 合并文件
      async checkAllChunksUploaded() {
        //暂停之后会合并已上传的文件（不影响后续继续上传合成完整文件）
        const allDone = this.chunks.every(chunk => chunk.status === 'done');
        if (allDone) {
          const fileName = this.file.name; // 从已上传的文件信息中获取文件名
          const totalChunks = this.chunks.length; // 总分片数

          axios.post(this.requestUrl + '/upload/merge', {
            fileName: fileName,
            chunks: totalChunks,
            identifier: this.identifier // 添加identifier参数       
          })
            .then(response => {
              console.log('Merge response:', response.data);
              // 处理合并成功的逻辑
              this.isUploading = false;
            })
            .catch(error => {
              alert("出错了，请联系管理员")
              console.error('Failed to merge file:', error);
            });
        }
      },
      //合并失败调用删除接口，删除分片文件
      deleteFileChunks(fileName, identifier) {
        axios.delete(this.requestUrl + '/upload/files/delete', { params: { fileName: fileName, identifier: identifier } })
          .then(response => {
            console.log('Delete response:', response.data);
          })
          .catch(error => {
            console.error('Failed to delete file chunks:', error);
          });
      },
    }
  })
</script>
<style scoped>
  .el-progress__text {
    width: 10%;
  }
</style>

</html>